package pCloud.acesscontrol;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pCloud.PCloudAttributeAndType;
import pCloud.Utilities;
import pCloud.config.PCloudConfig;
import pCloud.message.DatabaseReply;
import pCloud.message.StreamServerReply;
import pCloud.message.ReplyMessage;
import pCloud.obligation.ApproximationObligation;
import pCloud.obligation.DataProcessingObligation;
import pCloud.obligation.SlidingWindowObligation;
import pCloud.obligation.StreamProcessingObligation;
import pCloud.server.DataStreamHandle;
import pCloud.server.DatabaseHandle;
import pCloud.service.AuroraDataStreamHandle;
import pCloud.service.SQLDatabaseHandle;


import com.sun.xacml.Indenter;
import com.sun.xacml.Obligation;
import com.sun.xacml.ParsingException;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.IntegerAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;
import com.sun.xacml.ctx.ResponseCtx;
import com.sun.xacml.ctx.Result;



/**
 * PEP for PCloud. It first gets the decision from the PDP, then performs the
 * obligations.
 * 
 */
public class PCloudPEP {

	PCloudPDP pdp;

	public PCloudPEP(PCloudPDP pdp) {
		this.pdp = pdp;
	}

	public Object evaluateRequest(int requestId, String keyColumn,
			String requestFile, DatabaseHandle databaseHandler,
			DataStreamHandle datastreamHandler)
			throws Exception {
		pdp.setRequestCtx(requestFile);

		RequestCtx requestCtx = pdp.getRequestContext();

		long start = System.currentTimeMillis();
		ResponseCtx outputs = pdp.evaluate();
		long end = System.currentTimeMillis();
		
		System.out.println("PDP takes " + (end-start)*1.0/1000 + " seconds");
		
		double pdptime = (end-start)*1.0/1000;

		Set results = outputs.getResults();

		java.util.Iterator it = results.iterator();
		Result result = null;
		int decision = Result.DECISION_NOT_APPLICABLE;

		ReplyMessage replyMessage = null;
		
		

		// Assume that there's only one result.
		while (it.hasNext()) {
			result = (Result) it.next();
			decision = result.getDecision();
			if (decision != Result.DECISION_PERMIT) {
				replyMessage = new ReplyMessage(requestId, decision);
			}
		}

		// if the action is show_table or show_column, query the meta data
		// else processing the obligations.
		if (decision == Result.DECISION_PERMIT) {
			
		//	System.out.println("Decision is Permit!!!!");
			Set actions = requestCtx.getAction();
			ArrayList<AttributeValue> attValues = Utilities.getAttributeValue(
					PCloudAttributeAndType.ACTION_ID, actions);

			// assume there's only one action
			String att = ((StringAttribute) attValues.get(0)).getValue();
			if (att.equals("show_table") || att.equals("show_column")) {

				DatabaseReply dr = null;
				if (att.equals("show_table"))
					dr = ((SQLDatabaseHandle) databaseHandler)
							.constructShowTableReplyMessage();
				else
					dr = ((SQLDatabaseHandle) databaseHandler)
							.constructShowColumnReplyMessage(this
									.getTableNameFromRequestCtx(requestCtx));

				// System.out.println(dr.toString());
				replyMessage = new ReplyMessage(requestId, decision, dr);

				replyMessage
						.setMatchingPolicyIds(result.getMatchingPolicyIds());
				return replyMessage;
			}

			
			
			System.out.println("Building obligation map....");
			// 1. Build an obligation map
			HashMap<URI, Obligation> obligationMap = this
					.getObligations(result);
			if (obligationMap.containsKey(new URI(PCloudAttributeAndType.OBLIGATION_STREAM_FILTERING_ID)) ||
				obligationMap.containsKey(new URI(PCloudAttributeAndType.OBLIGATION_STREAM_MAPPING_ID)) ||
				obligationMap.containsKey(new URI(PCloudAttributeAndType.OBLIGATION_STREAM_WINDOW_AGGREGATION_ID))){
			
				//wwq: Stream Processing Obligation
						
				StreamProcessingObligation spo = new StreamProcessingObligation();
				//spo.processObligation return information 	
				System.out.println("Start Processing Stream DataStreams");
				
				StreamServerReply ssr = (StreamServerReply)spo.processObligation(datastreamHandler, result.getObligations(), requestCtx);
				ssr.pdptime = pdptime;
				replyMessage= new ReplyMessage(requestId, decision, ssr);
				
			}
			else if (obligationMap.containsKey(new URI(
					PCloudAttributeAndType.OBLIGATION_APPROXIMATION_PARAM))
					|| obligationMap
							.containsKey(new URI(
									PCloudAttributeAndType.OBLIGATION_APPROXIMATION_VALUE))) {
				ApproximationObligation ao = new ApproximationObligation();
				replyMessage = new ReplyMessage(requestId, decision,
						(DatabaseReply) ao.processObligation(keyColumn,
								databaseHandler, obligationMap, requestCtx));
			} else if (obligationMap.containsKey(new URI(
					PCloudAttributeAndType.OBLIGATION_SLIDING_WINDOW_ID))) {
				SlidingWindowObligation swo = new SlidingWindowObligation();
				replyMessage = new ReplyMessage(requestId, decision,
						(DatabaseReply) swo.processObligation(keyColumn,
								databaseHandler, obligationMap, requestCtx));
			} else {
				// 2. For now, assume there's only simple data processing
				// obligation
				DataProcessingObligation dpo = new DataProcessingObligation();
				replyMessage = new ReplyMessage(requestId, decision,
						(DatabaseReply) dpo.processObligation(keyColumn,
								databaseHandler, obligationMap, requestCtx));
			}
		}

		replyMessage.setMatchingPolicyIds(result.getMatchingPolicyIds());
		return replyMessage;
	}

	private String getTableNameFromRequestCtx(RequestCtx request)
			throws Exception {

		// get table ID from the resources
		Iterator it = request.getResource().iterator();
		Attribute att;
		while (it.hasNext()) {
			att = (Attribute) it.next();
			if (att.getId().equals(
					new URI(PCloudAttributeAndType.RESOURCE_TABLE_ID))) {
				return ((StringAttribute) att.getValue()).getValue();
			}
		}
		return null;
	}

	private HashMap<URI, Obligation> getObligations(Result result) {
		HashMap<URI, Obligation> obligationMap = new HashMap<URI, Obligation>();
		Iterator it = result.getObligations().iterator();
		Obligation ob;
		while (it.hasNext()) {
			ob = (Obligation) it.next();
			obligationMap.put(ob.getId(), ob);
		}
		return obligationMap;
	}

	protected String getDecision(int dec) {
		String res;
		if (dec == Result.DECISION_DENY)
			res = "Deny";
		else if (dec == Result.DECISION_INDETERMINATE)
			res = "Intermediate";
		else if (dec == Result.DECISION_NOT_APPLICABLE)
			res = "Not Applicable";
		else
			res = "Permit";
		return res;
	}

}
